/* Copyright (c) 2011-2013 ISP RAS, Moscow. For more information please write to

      mermakov@ispras.ru
      svartanov@ispras.ru

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package ru.ispras.coffeemachine.trace;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

public class PostProcessor
{
	String codePositions;
	String trace;

	private static String pcPrefix = "#PC ";

	public PostProcessor(String codePositions, String trace)
	{
		this.codePositions = codePositions;
		this.trace = trace;
	}

	public String postprocess()
	{
		List<Line> newTrace = new ArrayList<Line>();

		Map<Integer, String> numbers = new HashMap<Integer, String>();
		Map<String, Integer> maxMethodPC = new HashMap<String, Integer>();
		Map<String, Block> locked = new HashMap<String, Block>();

		try
		{
			BufferedReader reader = new BufferedReader(new StringReader(codePositions));

			String line = "";
			int lineNumber = 0;

			// Code positions reading

			while ((line = reader.readLine()) != null)
			{
				StringTokenizer tokenizer = new StringTokenizer(line, " ");

				if (line.length() >= pcPrefix.length() && line.startsWith(pcPrefix))
				{
					tokenizer.nextToken();
					int number = Integer.parseInt(tokenizer.nextToken(), 0x10);
					tokenizer.nextToken();
					String method = tokenizer.nextToken();

					numbers.put(number, method);
					maxMethodPC.put(method, number);
				}
			}

			// Events reading

			reader = new BufferedReader(new StringReader(trace));

			while ((line = reader.readLine()) != null)
			{
				if (line.trim().charAt(0) == '#') continue;

				StringTokenizer tokenizer = new StringTokenizer(line, " ");

				String command = tokenizer.nextToken();
				int threadNumber = Integer.parseInt(tokenizer.nextToken());
				int codePosition = Integer.parseInt(tokenizer.nextToken(), 0x10);
				String object = tokenizer.nextToken();
				int fifth = Integer.parseInt(tokenizer.nextToken());

				String method = numbers.get(codePosition);

				if (method == null) { throw new ParseException(lineNumber, "unknown code position " + codePosition); }

				if (command.equals("WRITER_LOCK"))
				{
					if (locked.containsKey(object))
					{
						String wasMethod = locked.get(object).methodName;
						int wasThread = locked.get(object).threadNumber;

						for (int i = newTrace.size() - 1; i >= 0; i--)
						{
							Line l = newTrace.get(i);

							if (l.methodName.equals(wasMethod) && l.threadNumber == wasThread)
							{
								newTrace.add(i + 1, new Line("UNLOCK", wasThread, maxMethodPC.get(wasMethod), object, 0, wasMethod));
								break;
							}
						}
					}
					locked.put(object, new Block(threadNumber, method));
				}

				if (command.equals("UNLOCK"))
				{
					Block block = locked.remove(object);

					if (!block.methodName.equals(method) || block.threadNumber != threadNumber)
					{
						System.out.println("Block error.");
					}
				}
				newTrace.add(new Line(command, threadNumber, codePosition, object, fifth, method));
				lineNumber++;
			}
		}
		catch (IOException e)
		{
			System.out.println("Error: reading trace during postprocessing.");
		}
		catch (ParseException e)
		{
			System.out.println("Error: " + e.message + " on line " + e.lineNumber);
		}
		StringBuffer result = new StringBuffer();

		for (Line l : newTrace)
		{
			result.append(l.toString());
		}
		return result.toString();
	}

	@SuppressWarnings("unused")
	private String spaces(int size)
	{
		String r = "";

		while (size > 0)
		{
			r += "    ";
			size--;
		}
		return r;
	}

	class Line
	{
		String command;
		int threadNumber;
		int codePosition;
		String forth;
		int fifth;

		String methodName;

		public Line(String command, int threadNumber, int codePosition, String forth, int fifth, String methodName)
		{
			this.command = command;
			this.threadNumber = threadNumber;
			this.codePosition = codePosition;
			this.forth = forth;
			this.fifth = fifth;
			this.methodName = methodName;
		}

		@Override
		public String toString()
		{
			return command + " " + Integer.toHexString(threadNumber) + " " + Integer.toHexString(codePosition) + " " + forth + " " + Integer.toHexString(fifth) + "\n";
		}
	}

	class Block
	{
		int threadNumber;
		String methodName;

		public Block(int threadNumber, String methodName)
		{
			this.threadNumber = threadNumber;
			this.methodName = methodName;
		}
	}

	class ParseException extends Exception
	{
		private static final long serialVersionUID = 1L;

		private final int lineNumber;
		private final String message;

		public ParseException(int lineNumber, String message)
		{
			this.lineNumber = lineNumber;
			this.message = message;
		}
	}
}
